package away3d.animators.skeleton ;


import away3d.core.math.Quaternion ;
import away3d.library.assets.AssetType ;
import away3d.library.assets.IAsset ;
import away3d.library.assets.NamedAssetBase ;
import flash.geom.Vector3D ;



/**
 * A SkeletonPose is a collection of JointPose objects, determining the pose for an entire skeleton.
 * A SkeletonPose and JointPose combination corresponds to a Skeleton and Joint combination. However, there is no
 * reference to a Skeleton instance, since several skeletons could be influenced by the same pose (fe: animation
 * sequences that can apply to any target with a valid skeleton)
 */
class SkeletonPose extends NamedAssetBase implements IAsset
{
    
    
    /**
     * The joint poses for the skeleton. The JointPoses indices correspond to the target skeleton's joints.
     */
    public var jointPoses : Vector<JointPose> ;
    
    
    
    /**
     * Creates a new SkeletonPose object.
     * @param numJoints The number of joints in the target skeleton.
     */
    public function new()
    {
        
        jointPoses = new Vector() ;
        
    }
    
    
    public function assetType( get_assetType, null ): String ;
    
    
    private function get_assetType() : String
    {
        
        return AssetType.SKELETON_POSE;
        
    }
    
    
    /**
     * Returns the JointPose, given the joint name.
     * @param jointName is the name of the JointPose to be found.
     * @return JointPose 
     */
    public function jointPoseFromName( jointName: String ) : JointPose
    {
        
        var jointPoseIndex:int = jointPoseIndexFromName( jointName ) ;
        
        if( jointPoseIndex != -1 )
        {
            
            return jointPoses[ jointPoseIndex ] ;
            
        }
        
        return null;
        
    }
    
    
    /**
     * Returns the joint index, given the joint name. -1 is returned if joint name not found.   
     * @param jointName is the name of the JointPose to be found.
     * @return jointIndex 
     */ 
    public function jointPoseIndexFromName(jointName:String):int
    {
        
        // this function is implemented as a linear search, rather than a possibly
        // more optimal method (Dictionary lookup, for example) because:
        // a) it is assumed that it will be called once for each joint
        // b) it is assumed that it will be called only during load, and not during main loop
        // c) maintaining a dictionary (for safety) would dictate an interface to access JointPoses,
        //    rather than direct array access.  this would be sub-optimal.
        var jointPoseIndex:Int;
        
        for( jointPose in jointPoses )
        {
            
            if( jointPose.name == jointName )
            {
                
                return jointPoseIndex;
            }
            
            jointPoseIndex++;
            
        }
        
        return -1;
        
    }
    
    
    public var numJointPoses( get_numJointPoses, null ) : Int ;
    
    
    /**
     * The amount of joints in the Skeleton
     */
    public function get_numJointPoses() : Int
    {
        
        return jointPoses.length;
        
    }
    
    
    /**
     * Clones this SkeletonPose, with all of its component jointPoses. 
     * @return SkeletonPose 
     */
    public function clone() : SkeletonPose
    {
        
        var clone           = new SkeletonPose() ;
        var numJointPoses   = jointPoses.length ;
        for( i in 0...numJointPoses )
        {
            var cloneJointPose      = new JointPose() ;
            var thisJointPose       = jointPoses[ i ] ;
            cloneJointPose.name     = thisJointPose.name ;
            
            cloneJointPose.copyFrom( thisJointPose ) ;
            
            clone.jointPoses[ i ]   = cloneJointPose ;
        }
        
        return clone;
        
    }
    
    
    /**
     * Converts a local hierarchical skeleton pose to a global pose
     * @param targetPose The SkeletonPose object that will contain the global pose.
     * @param skeleton The skeleton containing the joints, and as such, the hierarchical data to transform to global poses.
     */
    public function toGlobalPose( targetPose : SkeletonPose, skeleton : Skeleton )
    {
        
        var globalPoses                     = targetPose.jointPoses;
        var globalJointPose : JointPose;
        var joints                          = skeleton.joints;
        var len                             = numJointPoses;
        var parentIndex     : Int ;
        var joint           : SkeletonJoint ;
        var parentPose      : JointPose ;
        var pose            : JointPose ;
        var or              : Quaternion ;
        var tr              : Vector3D<Float> ;// TODO: check 
        var t               : Vector3D<Float> ;// TODO: check 
        var q               : Quaternion ;
        
        var x1              : Float ;
        var y1              : Float ; 
        var z1              : Float ;
        var w1              : Float ;
        var x2              : Float ;
        var y2              : Float ;
        var z2              : Float ;
        var w2              : Float ;
        var x3              : Float ;
        var y3              : Float ;
        var z3              : Float ;
        
        // :s
        if( globalPoses.length != len ) globalPoses.length = len ;
        
        for( i in 0...len )
        {
            
            globalJointPose     = globalPoses[ i ] ; 
            
            if( globalPoses == null )
            {
                
                globalJointPose = new JointPose() ;
                globalPoses[ i ] = globalJointPose ;
                
            }
            
            joint               = joints[ i ] ;
            parentIndex         = joint.parentIndex ;
            pose                = jointPoses[ i ] ;
            q                   = globalJointPose.orientation ;
            t                   = globalJointPose.translation ;
            
            if( parentIndex < 0 )
            {
                
                tr  = pose.translation ;
                or  = pose.orientation ;
                q.x = or.x ;
                q.y = or.y ;
                q.z = or.z ;
                q.w = or.w ;
                t.x = tr.x ;
                t.y = tr.y ;
                t.z = tr.z ;
                
            }
            else
            {
                
                // append parent pose
                parentPose  = globalPoses[ parentIndex ] ;
                
                // rotate point
                or          = parentPose.orientation ;
                tr          = pose.translation ;
                x2          = or.x ; 
                y2          = or.y ; 
                z2          = or.z ; 
                w2          = or.w ;
                x3          = tr.x ;
                y3          = tr.y ;
                z3          = tr.z ;
                
                w1          = -x2*x3 - y2*y3 - z2*z3 ;
                x1          = w2*x3 + y2*z3 - z2*y3 ;
                y1          = w2*y3 - x2*z3 + z2*x3 ;
                z1          = w2*z3 + x2*y3 - y2*x3 ;
                
                // append parent translation
                tr          = parentPose.translation ;
                
                t.x         = -w1*x2 + x1*w2 - y1*z2 + z1*y2 + tr.x ;
                t.y         = -w1*y2 + x1*z2 + y1*w2 - z1*x2 + tr.y ;
                t.z         = -w1*z2 - x1*y2 + y1*x2 + z1*w2 + tr.z ;
                
                // append parent orientation
                x1          = or.x ;
                y1          = or.y ;
                z1          = or.z ;
                w1          = or.w ;
                or          = pose.orientation ;
                x2          = or.x ;
                y2          = or.y ;
                z2          = or.z ;
                w2          = or.w ;
                
                q.w         = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2 ;
                q.x         = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2 ;
                q.y         = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2 ;
                q.z         = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2 ;
            
            }
            
        }
    
    }
    
    
}